#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using KLib.Physics;
using KLib.Physics.Collisions;
using KLib.Graphics;
#endregion

namespace KLib.Utilities
{
    public class PhysicsVisualDebugger : KLib.DrawableGameComponent
    {
        #region Fields
        private PhysicsManager physics;
        private SpriteRenderer batch;
        Texture2D gridTex;
        Texture2D aabbTex;
        Texture2D contactTex;
        Texture2D axisTex;
        Texture2D vertexTex;
        Matrix camMatrix = Matrix.Identity;


        #region Display Booleans
        private bool drawBounds = true;
        private bool drawGrid = true;
        private bool drawContacts = true;
        private bool drawVertices = true;
        #endregion

        #endregion

        #region Properties
        public bool DrawBoundsEnabled
        {
            get { return drawBounds; }
            set { drawBounds = value; }
        }

        public bool DrawGridEnabled
        {
            get { return drawGrid; }
            set { drawGrid = value; }
        }

        public bool DrawContactsEnabled
        {
            get { return drawContacts; }
            set { drawContacts = value; }
        }

        public bool DrawVerticesEnabled
        {
            get { return drawVertices; }
            set { drawVertices = value; }
        }
        #endregion

        #region Initialization

        public PhysicsVisualDebugger(Game game)
            : base(game)
        {
            physics = game.Services.GetService(typeof(IPhysicsService)) as PhysicsManager;

            if (physics == null)
                throw new NullReferenceException("Game does not have a required IPhysicsService service. Please add one before initializing the PhysicsVisualDebugger");
        }

        public override void Initialize()
        {
            // TODO: Add your initialization code here

            base.Initialize();
            batch = new SpriteRenderer(GraphicsDevice, Game);
            gridTex = TextureGenerator.GenerateRectangle(2, 2, Color.White,Color.Black,0,0.5f);
            contactTex = TextureGenerator.GenerateCircle(5, Color.Red, Color.Red, 1, 0.5f, 1f);
            axisTex = TextureGenerator.GenerateRectangle(2, 16, Color.Black);
            vertexTex = TextureGenerator.GenerateCircle(4, Color.Black, Color.Black, 1, 0.3f, 1);
        }

        protected override void LoadContent()
        {
            base.LoadContent();
        }

        #endregion

        #region Methods
        public override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here

            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            batch.Begin();
            foreach (Body body in physics.Bodies)
            {
                RigidBody rigid = body as RigidBody;
                if (rigid == null)
                    break;

                DrawGrid(rigid);
                DrawBoundingBox(rigid);
                DrawVertices(rigid);
            }

            DrawContacts();


            batch.End();
            base.Draw(gameTime);
        }

        public void SetCameraMatrix(Camera.Camera camera)
        {
            Vector3 rotationOrigin = new Vector3(camera.Position + camera.CenterScreen, 0);

            this.camMatrix = Matrix.CreateTranslation(-rotationOrigin)
                    * Matrix.CreateScale(camera.ZoomAmount.X,camera.ZoomAmount.Y, 1.0f)
                    * Matrix.CreateRotationZ(camera.Rotation)
                    * Matrix.CreateTranslation(camera.CenterScreen.X, camera.CenterScreen.Y, 0.0f);
        }
        #endregion

        #region Helper Methods


        private void DrawBoundingBox(RigidBody body)
        {
            if (!drawBounds) return;
            AABB bounds = body.Bounds;
            if (physics.collisions.Contains(body))
                aabbTex = TextureGenerator.GenerateRectangle((int)bounds.Width, (int)bounds.Height, Color.TransparentBlack, Color.DarkRed, 4, 0f, 0.5f);
            else
                aabbTex = TextureGenerator.GenerateRectangle((int)bounds.Width, (int)bounds.Height, Color.TransparentBlack, Color.DarkGreen, 4, 0f, 1f);
            
                batch.Draw(aabbTex, Vector2.Transform(bounds.Min, camMatrix), Color.White);

        }

        private void DrawGrid(RigidBody rigid)
        {
            if (!drawGrid) return;
            for (int g = 0; g < rigid.Geometry.Count; g++)
            {
                for (int i = 0; i < rigid.Geometry[g].Grid.Points.Length; i++)
                {
                    if (rigid.Geometry[g].Grid.PointValues[i] > 0)
                        batch.Draw(gridTex, Vector2.Transform(rigid.Geometry[g].GetWorldCoordinates(rigid.Geometry[g].Grid.Points[i]), camMatrix), Color.Red);
                    else if (rigid.Geometry[g].Grid.PointValues[i] < 0)
                        batch.Draw(gridTex, Vector2.Transform(rigid.Geometry[g].GetWorldCoordinates(rigid.Geometry[g].Grid.Points[i]), camMatrix), Color.LightGreen);
                    else
                        batch.Draw(gridTex, Vector2.Transform(rigid.Geometry[g].GetWorldCoordinates(rigid.Geometry[g].Grid.Points[i]), camMatrix), Color.Black);
                }
            }
        }

        private void DrawContacts()
        {
            if (!drawContacts) return;
            foreach (Arbiter2 arbiter in physics.ArbiterList)
            {
                foreach (Contact contact in arbiter.ContactList)
                {
                    batch.Draw(contactTex, Vector2.Transform(contact.Position - Vector2.One * (contactTex.Width / 2f), camMatrix), Color.White);
                }
            }
        }

        private void DrawVertices(RigidBody body)
        {
            if (!drawVertices) return;
            for (int i = 0; i < body.Geometry.Count; i++)
            {
                foreach (Vector2 vert in body.Geometry[i].WorldVertices)
                {
                    batch.Draw(vertexTex, Vector2.Transform(vert - Vector2.One * (vertexTex.Width / 2f), camMatrix));
                }
            }
        }
        #endregion
    }
}